package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"sync"
	"time"

	"../util"
	log "github.com/kpango/glg"
)

type Authenticator struct {
	password map[string]string
	watcher  *util.FileWatcher
	mu       *sync.RWMutex
}

func NewAuthenticator(fileName string) (*Authenticator, error) {
	auth := &Authenticator{
		password: make(map[string]string),
		watcher:  new(util.FileWatcher),
		mu:	      new(sync.RWMutex),
	}

	watcher, err := util.NewWatcher()
	if err != nil {
		return auth, fmt.Errorf("auth.NewWatcher: %v", err)
	}
	auth.watcher = watcher
	err = auth.LoadPassword(fileName)
	if err != nil {
		return auth, err
	}
	return auth, nil
}

func (a *Authenticator) LoadPassword(fileName string) error {
	a.mu.Lock()
	defer a.mu.Unlock()
	encodeText, err := ioutil.ReadFile(fileName)
	if err != nil {
		return fmt.Errorf("auth.LoadPassword: Failed to read password config: %v", err)
	}
	decodeText, err := base64.StdEncoding.DecodeString(string(encodeText))
	if err != nil {
		return fmt.Errorf("auth.Decode: Fail to decode password config: %v", err)
	}
	var passwd map[string]string
	err = json.Unmarshal(decodeText, &passwd)
	if err != nil {
		return fmt.Errorf("auth.LoadPassword: Failed to parse password config: %v", err)
	}
	a.password = passwd
	log.Infof("Load password success.")
	return nil
}

func (a *Authenticator) ReloadIfModified(filename string) {
	a.watcher.Watch(filename, func(filename string) {
		log.Infof("file %s changed, reload\n", filename)
		err := a.LoadPassword(filename)
		if err != nil {
			log.Errorf("[Error] %v", err)
		}
	})
}

func (a *Authenticator) CloseWatcher() {
	a.watcher.Close()
}

// HTTP api
func (a *Authenticator) Authenticate(r *http.Request) error {
	timestamp := r.FormValue("timestamp")
	signature := r.FormValue("signature")
	user := r.FormValue("user")
	if timestamp == "" || signature == "" || user == "" {
		return fmt.Errorf("认证失败，请求参数错误。")
	}
	t, err := strconv.ParseInt(timestamp, 10, 64)
	if err != nil {
		return fmt.Errorf("认证失败，时间戳解析错误: %v", err)
	}
	tn := time.Now().UnixNano() / 1000 / 1000
	if tn-t > 5000 || tn-t < 0 {
		return fmt.Errorf("认证失败，时间戳无效。")
	}
	return a.Auth(user, timestamp, signature)
}

func (a *Authenticator) Auth(user, timestamp, signature string) error {
	password := a.password[user]
	if password == "" {
		return fmt.Errorf("认证失败，账号错误。")
	}
	text := user + password + timestamp
	if util.MD5([]byte(text)) != signature {
		return fmt.Errorf("认证失败，账号或密码错误。")
	}
	return nil
}
